/**
 * 对伙伴系统的简单测试。
 */
#include "test_tools.h"
#include <mm/early_alloc.h>
#include <mm/pages.h>
#include <mm/page_alloc.h>
#include <mm/physical_area.h>
#include <mm/memory.h>

/// 分配16MB
#define V_MEM_SIZE  (16UL << 20)

#define V_PAGE_ORDER 12

/* 获得一个内存区域的所有空闲链表的计数。 */
static void __get_zone_free_list_count(
    unsigned long zone_num,
    unsigned long *buffer)
{
    struct zone_struct *zone = get_current_pglist_zone(zone_num);
    for (unsigned long i = 0; i < PAGE_MAX_ORDER; i++) {
        buffer[i] = get_free_list_count(zone, i);
    }
}

/* 展示所有区域内所有空闲链表的计数。 */
static void __show_free_list_count(void)
{
    unsigned long count = 0;
    for (unsigned long z = 0; z < 2; z++) {
        printf("zone %lu:\n", z);
        struct zone_struct *zone = get_current_pglist_zone(z);
        for (unsigned long i = 0; i < PAGE_MAX_ORDER; i++) {
            printf("order %lu: %lu\t", i, get_free_list_count(zone, i));
            count += get_free_list_count(zone, i) << i;
        }
        printf("\n");
    }
    printf("total free pages: %lu\n", count);
}

/* 计算当前总的空闲页数。 */
static unsigned long __calculate_free_pages(void)
{
    unsigned long count = 0;
    for (unsigned long z = 0; z < 2; z++) {
        struct zone_struct *zone = get_current_pglist_zone(z);
        for (unsigned long i = 0; i < PAGE_MAX_ORDER; i++) {
            count += get_free_list_count(zone, i) << i;
        }
    }
    return count;
}

/// 第一个测试。
static void test1(void)
{
    /// 分配测试用的内存区域。
    void *v_mem = aligned_alloc(V_MEM_SIZE, V_MEM_SIZE);
    if (v_mem != NULL) {
        memset(v_mem, 0, V_MEM_SIZE);
    }
    unsigned long page_start = (VIRT_TO_PHY(v_mem) >> 12);
    unsigned long page_end = page_start + (V_MEM_SIZE >> 12);
    
    printf("%lu %lu\n", page_start, page_end);
    /// 初始化前期内存分配器。
    if (early_alloc_init(page_start, page_end, page_start, page_end, 12)) {
        LOG_TEST_FAIL("failed to initialize early_alloc!\n");
    } else {
        LOG_TEST_PASS("early_alloc initilaized!\n");
    }

    if (pages_init(VIRT_TO_PHY(v_mem), VIRT_TO_PHY(v_mem) + V_MEM_SIZE, 0)) {
        LOG_TEST_FAIL("failed to initialize page struct!\n");
    } else {
        LOG_TEST_PASS("page struct initilaized!\n");
    }
    if (set_pages_valid(VIRT_TO_PHY(v_mem), VIRT_TO_PHY(v_mem) + V_MEM_SIZE)) {
        LOG_TEST_FAIL("failed to make page struct valid!\n");
    } else {
        LOG_TEST_PASS("page struct are all valid!\n");
    }

    /// 添加内存区域。
    if (add_physical_area(VIRT_TO_PHY(v_mem), (6UL << 20))) {
        LOG_TEST_FAIL("failed to add physical area!\n");
    } else {
        LOG_TEST_PASS("successfully added physical area!\n");
    }
    if (add_physical_area(VIRT_TO_PHY(v_mem) + (6UL << 20), (2UL << 20))) {
        LOG_TEST_FAIL("failed to add physical area!\n");
    } else {
        LOG_TEST_PASS("successfully added physical area!\n");
    }

    /// 初始化pglist
    if (pglist_init(1)) {
        LOG_TEST_FAIL("failed to initialize pglists!\n");
    } else {
        LOG_TEST_PASS("pglists initilaized!\n");
    }

    /// 初始化内存区域结构体。
    if (mem_zones_init()) {
        LOG_TEST_FAIL("failed to initialize mem_zones!\n");
    } else {
        LOG_TEST_PASS("mem_zones initilaized!\n");
    }

    /// 激活伙伴系统。
    if (activate_buddy_system()) {
        LOG_TEST_FAIL("failed to activate buddy system!\n");
    } else {
        LOG_TEST_PASS("buddy system activated!\n");
    }

    __show_free_list_count();

    struct page_struct *tmp_pages[PAGE_MAX_ORDER][4] = { 0 };
    unsigned long tmp_pages_count[PAGE_MAX_ORDER] = { 0 };
    unsigned long free_list_buffer1[PAGE_MAX_ORDER] = { 0 };
    unsigned long free_list_buffer2[PAGE_MAX_ORDER] = { 0 };
    /// 记录最开始空闲页的数量。
    unsigned long free_pages_total = __calculate_free_pages();
    unsigned long free_pages_remain = 0;

    /// 规避分割的页分配。
    __get_zone_free_list_count(0, free_list_buffer1);
    __get_zone_free_list_count(1, free_list_buffer2);

    for (unsigned long i = 0; i < PAGE_MAX_ORDER; i++) {
        while (free_list_buffer1[i] > 0) {
            struct page_struct *tmp_page = alloc_pages(i);

            if (tmp_page == NULL) {
                printf("failed to get order: %lu in zone 0\n", i);
            }

            free_list_buffer1[i] -= 1;
            tmp_pages[i][tmp_pages_count[i]] = tmp_page;
            tmp_pages_count[i] += 1;
        }
    }

    /// zone 0
    for (unsigned long i = 0; i < PAGE_MAX_ORDER; i++) {
        while (free_list_buffer1[i] > 0) {
            struct page_struct *tmp_page = alloc_pages(i);

            if (tmp_page == NULL) {
                printf("failed to get order: %lu in zone 0\n", i);
            }

            free_list_buffer1[i] -= 1;
            tmp_pages[i][tmp_pages_count[i]] = tmp_page;
            tmp_pages_count[i] += 1;
        }
    }
    /// zone 1
    for (unsigned long i = 0; i < PAGE_MAX_ORDER; i++) {
        while (free_list_buffer2[i] > 0) {
            struct page_struct *tmp_page = alloc_pages(i);

            if (tmp_page == NULL) {
                printf("failed to get order: %lu in zone 1\n", i);
            }

            free_list_buffer2[i] -= 1;
            tmp_pages[i][tmp_pages_count[i]] = tmp_page;
            tmp_pages_count[i] += 1;
        }
    }

    free_pages_remain = __calculate_free_pages();
    if (free_pages_remain) {
        LOG_TEST_FAIL("all pages are supposed to be allocated," 
            "but there's still free pages! remained: %lu\n",
            free_pages_remain);
        __show_free_list_count();
    } else {
        LOG_TEST_PASS("there's not free page remains!\n");
    }

    for (unsigned long i = 0; i < PAGE_MAX_ORDER; i++) {
        while (tmp_pages_count[i] > 0) {
            tmp_pages_count[i] -= 1;
            if (i > 1) {
                if (i % 2) {
                    free_pages(tmp_pages[i][tmp_pages_count[i]] + (1UL << (i - 1)), i - 2);
                } else {
                    free_pages(tmp_pages[i][tmp_pages_count[i]], i - 2);
                }
            }
            free_pages(tmp_pages[i][tmp_pages_count[i]], i);
            tmp_pages[i][tmp_pages_count[i]] = NULL;
        }
    }

    free_pages_remain = __calculate_free_pages();
    if (free_pages_remain != free_pages_total) {
        LOG_TEST_FAIL("not all pages are free!" 
            "remained: %lu, total: %lu\n",
            free_pages_remain,
            free_pages_total
        );
        __show_free_list_count();
    } else {
        LOG_TEST_PASS("all pages are free!\n");
    }


    /// 分配时规避分割，释放时一页一页地释放。
    __get_zone_free_list_count(0, free_list_buffer1);
    __get_zone_free_list_count(1, free_list_buffer2);

    for (unsigned long i = 0; i < PAGE_MAX_ORDER; i++) {
        while (free_list_buffer1[i] > 0) {
            struct page_struct *tmp_page = alloc_pages(i);

            if (tmp_page == NULL) {
                printf("failed to get order: %lu in zone 0\n", i);
            }

            free_list_buffer1[i] -= 1;
            tmp_pages[i][tmp_pages_count[i]] = tmp_page;
            tmp_pages_count[i] += 1;
        }
    }

    /// zone 0
    for (unsigned long i = 0; i < PAGE_MAX_ORDER; i++) {
        while (free_list_buffer1[i] > 0) {
            struct page_struct *tmp_page = alloc_pages(i);

            if (tmp_page == NULL) {
                printf("failed to get order: %lu in zone 0\n", i);
            }

            free_list_buffer1[i] -= 1;
            tmp_pages[i][tmp_pages_count[i]] = tmp_page;
            tmp_pages_count[i] += 1;
        }
    }
    /// zone 1
    for (unsigned long i = 0; i < PAGE_MAX_ORDER; i++) {
        while (free_list_buffer2[i] > 0) {
            struct page_struct *tmp_page = alloc_pages(i);

            if (tmp_page == NULL) {
                printf("failed to get order: %lu in zone 1\n", i);
            }

            free_list_buffer2[i] -= 1;
            tmp_pages[i][tmp_pages_count[i]] = tmp_page;
            tmp_pages_count[i] += 1;
        }
    }

    free_pages_remain = __calculate_free_pages();
    if (free_pages_remain) {
        LOG_TEST_FAIL("all pages are supposed to be allocated," 
            "but there's still free pages! remained: %lu\n",
            free_pages_remain);
        __show_free_list_count();
    } else {
        LOG_TEST_PASS("there's not free page remains!\n");
    }

    for (unsigned long i = 0; i < PAGE_MAX_ORDER; i++) {
        while (tmp_pages_count[i] > 0) {
            tmp_pages_count[i] -= 1;
            for (unsigned long j = 0; j < (1UL << i); j++) {
                free_page(tmp_pages[i][tmp_pages_count[i]] + j);
            }
            tmp_pages[i][tmp_pages_count[i]] = NULL;
        }
    }

    free_pages_remain = __calculate_free_pages();
    if (free_pages_remain != free_pages_total) {
        LOG_TEST_FAIL("not all pages are free!" 
            "remained: %lu, total: %lu\n",
            free_pages_remain,
            free_pages_total
        );
        __show_free_list_count();
    } else {
        LOG_TEST_PASS("all pages are free!\n");
    }


    /// 单页分配所有空闲页，再一次一页地全部释放掉。
    struct page_struct **pages = \
        malloc(sizeof(struct page_struct *) * free_pages_total);
    memset(pages, 0, sizeof(struct page_struct *) * free_pages_total);
    for (unsigned long i = 0; i < free_pages_total; i++) {
        struct page_struct *tmp_page = alloc_page();
        if (tmp_page == NULL) {
            printf("failed to get a single page! cnt: %lu\n", i);
        }
        pages[i] = tmp_page;
    }
    free_pages_remain = __calculate_free_pages();
    if (free_pages_remain) {
        LOG_TEST_FAIL("all pages are supposed to be allocated," 
            "but there's still free pages! remained: %lu\n",
            free_pages_remain);
        __show_free_list_count();
    } else {
        LOG_TEST_PASS("there's not free page remains!\n");
    }

    for (unsigned long i = 0; i < free_pages_total; i++) {
        free_page(pages[i]);
        pages[i] = NULL;
    }
    free_pages_remain = __calculate_free_pages();
    if (free_pages_remain != free_pages_total) {
        LOG_TEST_FAIL("not all pages are free!" 
            "remained: %lu, total: %lu\n",
            free_pages_remain,
            free_pages_total
        );
    } else {
        LOG_TEST_PASS("all pages are free!\n");
    }
    __show_free_list_count();

    free(v_mem);
}

int main(void)
{
    printf("** page_alloc.h test:\n");
    test1();
    return 0;
}
